﻿Imports System.Net
Imports System.IO
Imports System.Reactive
Imports System.Reactive.Linq
Imports CoreTweet
Imports CoreTweet.Streaming
Imports CoreTweet.Streaming.Reactive

Public Class TwitterClient
    Implements IDisposable

    Private Const ConsumerKey As String = "xxxxxxxxxxxxxxxxxxxxx"
    Private Const ConsumerSecret As String = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

    Private NetworkErrorWaitingTime As Integer
    Private HttpErrorWaitingTime As Integer = 5000 / 2 'msec
    Private LateLimitWaitingTime As Integer = 60000 / 2 'msec

    Private ReconnectCount As Integer = 0

    Private TwitterTokens As CoreTweet.Tokens
    Private TwitterConnection As IDisposable

    Public Property SearchWord As String

    Public Sub Connect(tokens As CoreTweet.Tokens)
        Me.Disconnect()
        Me.TwitterTokens = tokens
    End Sub

    Public Sub Reconnect(waitTime As Integer)
        ReconnectCount += 1
        Me.WriteLog(String.Format("Reconnect after {0} seconds.({1})", Math.Ceiling(waitTime / 1000), ReconnectCount))
        Me.Disconnect()

        System.Threading.Thread.Sleep(waitTime + 200)
        Me.DoStreaming()
    End Sub

    Public Sub DoStreaming()

        Dim filterStream = Me.TwitterTokens.Streaming.StartObservableStream(
            StreamingType.Filter, New StreamingParameters(Function(track) SearchWord)).Publish()

        Dim statusAction = Sub(message As StatusMessage)
                               Dim msg = Me.GetTweetMessage(message.Status)
                               If msg IsNot Nothing Then
                                   RaiseEvent MessageReceived(
                                       Me, New MessageReceiveEventArgs() With {.Tweet = msg})
                               End If
                           End Sub

        Dim errorAction = Sub(ex As Exception)
                              Dim msg = ex.Message

                              Dim tex = TryCast(ex, TwitterException)
                              If tex IsNot Nothing AndAlso tex.Status <> HttpStatusCode.OK Then
                                  If tex.Status = 420 Then
                                      'Rate Limited
                                      Me.Retry(msg, Me.DoubleEachLateLimitWait)
                                  Else
                                      Me.Retry(msg, Me.DoubleEachHttpErrorWait)
                                  End If

                              ElseIf TypeOf ex Is WebException Then
                                  'Network Error
                                  Dim wex = DirectCast(ex, WebException)
                                  Dim resp = TryCast(wex.Response, HttpWebResponse)
                                  If resp IsNot Nothing AndAlso resp.StatusCode = HttpStatusCode.Unauthorized Then
                                      Me.Abort(msg)
                                  Else
                                      Me.Retry(msg, Me.IncreaseNetworkErrorWait)
                                  End If

                              ElseIf TypeOf ex Is IOException Then
                                  'Streaming Error
                                  Me.Retry(msg, 100)
                              Else
                                  Me.WriteLog(msg)
                              End If
                          End Sub

        filterStream.OfType(Of StatusMessage).Subscribe(statusAction, errorAction)

        Me.TwitterConnection = filterStream.Connect()

    End Sub

    Public ReadOnly Property DoubleEachLateLimitWait() As Integer
        Get
            LateLimitWaitingTime += LateLimitWaitingTime
            Return LateLimitWaitingTime
        End Get
    End Property

    Public ReadOnly Property DoubleEachHttpErrorWait() As Integer
        Get
            HttpErrorWaitingTime = Math.Max(HttpErrorWaitingTime * 2, 320000)
            Return HttpErrorWaitingTime
        End Get
    End Property

    Public ReadOnly Property IncreaseNetworkErrorWait() As Integer
        Get
            NetworkErrorWaitingTime = Math.Max(NetworkErrorWaitingTime + 250, 16000)
            Return NetworkErrorWaitingTime
        End Get
    End Property

    Public Shared Function SingleUserAuthorization(settings As TwitTimelineSetting) As CoreTweet.Tokens
        Dim tokens = CoreTweet.Tokens.Create(
            ConsumerKey, ConsumerSecret,
            TwitTimelineSetting.Decrypt(settings.ToString, settings.AccessToken),
            TwitTimelineSetting.Decrypt(settings.AccessToken, settings.OAuthToken))

        Return tokens

    End Function

    Public Shared Function PinAuthorization() As PinAuthCredentials
        Dim session = CoreTweet.OAuth.Authorize(ConsumerKey, ConsumerSecret)
        Dim url = session.AuthorizeUri

        System.Diagnostics.Process.Start(url.AbsoluteUri)

        Dim pinCode = InputBox("Please input PIN code here.")
        Dim token = CoreTweet.OAuth.GetTokens(session, pinCode)

        Return New PinAuthCredentials With
               {
                   .AccessToken = token.AccessToken,
                   .OAuthToken = token.AccessTokenSecret
               }

    End Function

    Public Class PinAuthCredentials
        Public AccessToken As String
        Public OAuthToken As String
    End Class

    Public Sub Disconnect()

        If Me.TwitterConnection IsNot Nothing Then
            Me.TwitterConnection.Dispose()
            Me.TwitterConnection = Nothing
        End If

    End Sub

    Private Function GetTweetMessage(ByVal status As CoreTweet.Status) As TweetMessage

        If status Is Nothing Then
            Return Nothing
        End If

        Return New TweetMessage(status)

    End Function

    Private Sub WriteLog(message As String)
        RaiseEvent ErrorMessageReceived(
                    Me, New ErrorMessageReceiveEventArgs With {.ErrorMessage = message, .Status = ErrorStatus.Warn})
    End Sub

    Private Sub Retry(message As String, waitTime As Integer)
        RaiseEvent ErrorMessageReceived(
                    Me, New ErrorMessageReceiveEventArgs With {.ErrorMessage = message, .Status = ErrorStatus.Retry, .WaitTime = waitTime})
    End Sub

    Private Sub Abort(message As String)
        RaiseEvent ErrorMessageReceived(
                    Me, New ErrorMessageReceiveEventArgs With {.ErrorMessage = message, .Status = ErrorStatus.Abort})
    End Sub

    Public Event MessageReceived As EventHandler(Of MessageReceiveEventArgs)
    Protected Sub OnMessageReceived(ByVal e As MessageReceiveEventArgs)
        RaiseEvent MessageReceived(Me, e)
    End Sub

    Public Event ErrorMessageReceived As EventHandler(Of ErrorMessageReceiveEventArgs)
    Protected Sub OnErrorMessageReceived(e As ErrorMessageReceiveEventArgs)
        RaiseEvent ErrorMessageReceived(Me, e)
    End Sub

#Region "IDisposable Support"
    Private disposedValue As Boolean ' 重複する呼び出しを検出するには

    ' IDisposable
    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If Not Me.disposedValue Then
            If disposing Then
                Me.Disconnect()
            End If

            ' TODO: アンマネージ リソース (アンマネージ オブジェクト) を解放し、下の Finalize() をオーバーライドします。
            ' TODO: 大きなフィールドを null に設定します。
        End If
        Me.disposedValue = True
    End Sub

    Protected Overrides Sub Finalize()
        ' このコードを変更しないでください。クリーンアップ コードを上の Dispose(ByVal disposing As Boolean) に記述します。
        Dispose(False)
        MyBase.Finalize()
    End Sub

    ' このコードは、破棄可能なパターンを正しく実装できるように Visual Basic によって追加されました。
    Public Sub Dispose() Implements IDisposable.Dispose
        ' このコードを変更しないでください。クリーンアップ コードを上の Dispose(ByVal disposing As Boolean) に記述します。
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub
#End Region

End Class

Public Class MessageReceiveEventArgs
    Inherits EventArgs

    Public Property Tweet As TweetMessage

End Class

Public Class ErrorMessageReceiveEventArgs
    Inherits EventArgs

    Public Property ErrorMessage As String
    Public Property Status As ErrorStatus
    Public Property WaitTime As Integer

End Class

Public Enum ErrorStatus
    Warn
    Retry
    Abort
End Enum
